package paim.wingchun.view.componentes.table;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;

import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;

import paim.wingchun.app.Reflections;
import paim.wingchun.app.WC_Log;
import paim.wingchun.model.pojos.Pojo;

public class WC_TableModel extends AbstractTableModel {

    private static final long serialVersionUID = 1L;

    List<Object> COLUMNS = new ArrayList<>();

    /** coluna sequencial se houver sera sempre a primeira */
    private Vector<Object> sequencial = new Vector<>();

    /** coluna de selecoes se houver sera apos a sequencial */
    private List<Boolean> selecoes = new ArrayList<>();

    private final int selectionMode;

    /** colunas construtor, sem vinculos com o POJO, serï¿½ sempre o ultimo */
    private Vector<Object> construtor = new Vector<>();

    /** serao as colunas associadas ao pojo */
    private final List<Field> fields;

    private final Class<? extends Pojo> pClass;

    private List<Object> objetos = new ArrayList<>();

    enum TipoColuna {
        SEQUENCIAL, SELECTOR, FIELD, CONSTRUCTOR
    }

    // TODO torcar para integer tem muito cast
    Map<TipoColuna, List<Integer>> mapColunas = new HashMap<TipoColuna, List<Integer>>();

    public Map<TipoColuna, List<Integer>> getMapColunas() {
        return this.mapColunas;
    }

    public WC_TableModel(Class<? extends Pojo> pClass) {
        this(pClass, ListSelectionModel.SINGLE_SELECTION, true, false, false);
    }

    public WC_TableModel(Class<? extends Pojo> pClass, int selectionMode, boolean hasSequencial, boolean hasSeletor,
            boolean hasConstrutor) {
        this(pClass, Reflections.getDeepSimpleFields(pClass), selectionMode, hasSequencial, hasSeletor, hasConstrutor);
    }

    public WC_TableModel(Class<? extends Pojo> pClass, List<Field> fields, int selectionMode, boolean hasSequencial,
            boolean hasSeletor, boolean hasConstrutor) {
        this.pClass = pClass;
        this.fields = fields;
        this.selectionMode = selectionMode;

        mapeiaColunas(hasSequencial, hasSeletor, hasConstrutor);
    }

    private void mapeiaColunas(boolean hasSequencial, boolean hasSeletor, boolean hasConstrutor) {
        /* mapeia a coluna sequencial, que sera sempre a zero se houver */
        if ( hasSequencial ) {
            /* nome da coluna */
            this.COLUMNS.add("");
            List<Integer> tmp = new ArrayList<Integer>(1);
            tmp.add(0);
            /* mapeia */
            mapColunas.put(TipoColuna.SEQUENCIAL, tmp);
        }

        /* mapeia a coluna seletor se houver */
        if ( hasSeletor ) {
            List<Integer> tmp = new ArrayList<Integer>(1);
            tmp.add(COLUMNS.size());
            /* nome da coluna */
            this.COLUMNS.add("Seleï¿½ï¿½o");
            /* mapeia */
            mapColunas.put(TipoColuna.SELECTOR, tmp);
        }

        /* mapeia as colunas do POJO */
        {
            /* set nomes da colunas conforme classe POJO */
            List<String> rotulos = Reflections.getRotuloList(this.fields);
            List<Integer> tmp = new ArrayList<Integer>(3);
            for ( String rotulo : rotulos ) {
                tmp.add(COLUMNS.size());
                /* nome da coluna */
                this.COLUMNS.add(rotulo);
            }
            /* mapeia */
            mapColunas.put(TipoColuna.FIELD, tmp);
        }

        /* mapeia a coluna seletor se houver */
        if ( hasConstrutor ) {
            List<Integer> tmp = new ArrayList<Integer>(1);
            tmp.add(COLUMNS.size());
            /* nome da coluna */
            this.COLUMNS.add("");
            /* mapeia */
            mapColunas.put(TipoColuna.CONSTRUCTOR, tmp);
        }

    }

    public boolean haveSequencial() {
        return mapColunas.containsKey(TipoColuna.SEQUENCIAL);
    }

    public boolean haveSelector() {
        return mapColunas.containsKey(TipoColuna.SELECTOR);
    }

    public boolean haveConstructor() {
        return mapColunas.containsKey(TipoColuna.CONSTRUCTOR);
    }

    public int getSequencialColumn() {
        if ( haveSequencial() )
            return mapColunas.get(TipoColuna.SEQUENCIAL).get(0);
        return -1;
    }

    public int getSelectorColumn() {
        if ( haveSelector() )
            return mapColunas.get(TipoColuna.SELECTOR).get(0);
        return -1;
    }

    public int getConstructorColumn() {
        if ( haveConstructor() )
            return mapColunas.get(TipoColuna.CONSTRUCTOR).get(0);
        return -1;
    }

    @Deprecated
    public List<Integer> getSelectorColumns() {
        return mapColunas.get(TipoColuna.SELECTOR);
    }

    @Deprecated
    public List<Integer> getConstrutorColumns() {
        return mapColunas.get(TipoColuna.CONSTRUCTOR);
    }

    public List<Integer> getFieldColumns() {
        return mapColunas.get(TipoColuna.FIELD);
    }

    public Field getField(int columnIndex) {
        return fields.get(getFieldColumns().indexOf(columnIndex));
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {

        /* se for coluna sequencial */
        if ( haveSequencial() && getSequencialColumn() == columnIndex )
            return Integer.class;
        /* se for coluna seletor */
        if ( haveSelector() && getSelectorColumn() == columnIndex )
            return Boolean.class;

        /* se for coluna field */
        if ( getFieldColumns().contains(columnIndex) ) {
            return getField(columnIndex).getType();
        }

        /* se for coluna construtor */
        if ( haveConstructor() && getConstructorColumn() == columnIndex ) {
            // TODO implemente-me
            return Object.class;
        }

        return super.getColumnClass(columnIndex);
    }

    @Override
    public String getColumnName(int column) {
        return (String) COLUMNS.get(column);
    }

    @Override
    public int getColumnCount() {
        return COLUMNS.size();
    }

    @Override
    public int getRowCount() {
        return objetos.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if ( this.objetos == null )
            return null;

        if ( haveSequencial() && getSequencialColumn() == columnIndex )
            return rowIndex + 1;

        if ( haveSelector() && getSelectorColumn() == columnIndex )
            return selecoes.get(rowIndex);

        if ( getFieldColumns().contains(columnIndex) ) {
            Object objeto = this.objetos.get(rowIndex);
            Object retorno;
            try {
                retorno = Reflections.getValorField(objeto, getField(columnIndex));
            }
            catch ( Exception e ) {
                WC_Log.getInstancia().getlogger().log(Level.SEVERE, e.getMessage(), e);
                retorno = null;
            }
            return retorno;
        }

        if ( haveConstructor() && getConstructorColumn() == columnIndex ) {
            // TODO implemente-me
        }
        return null;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if ( haveSelector() && getSelectorColumn() == columnIndex ) {
            if ( ListSelectionModel.SINGLE_SELECTION == selectionMode )
                deselectAll();
            selecoes.set(rowIndex, (Boolean) aValue);
            fireTableCellUpdated(rowIndex, columnIndex);
        }
        else
            super.setValueAt(aValue, rowIndex, columnIndex);
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        if ( haveSelector() && getSelectorColumn() == columnIndex )
            return true;
        return super.isCellEditable(rowIndex, columnIndex);
    }

    public void setObjetos(List<?> objetos) {

        this.objetos = (List<Object>) objetos;

        /* adicionar itens na coluna seletor */
        if ( haveSelector() )
            for ( int i = 0; i < objetos.size(); i++ ) {
                selecoes.add(false);
            }
    }

    public List<?> getObjetos() {
        return this.objetos;
    }

    // TODO implemente-me
    public void removeAll(List<?> objetos) {
        // objetos.removeAll()
    }

    public Object remove(int row) {
        if ( row < 0 || row > objetos.size() )
            return null;

        Object o = objetos.remove(row);
        fireTableRowsDeleted(row, row);
        return o;
    }

    // TODO implemente-me
    public void clear() {
        objetos.clear();
        fireTableDataChanged();
    }

    public <T> T get(int index) {
        // return (T) get(index);
        if ( index < 0 || index > objetos.size() )
            return null;
        return (T) objetos.get(index);

    }

    public boolean addAll(List<?> objetos) {
        // clear();
        // addAll(objetos);

        if ( objetos == null || !List.class.isAssignableFrom(objetos.getClass()) )
            return false;
        boolean adicionou = this.objetos.addAll(objetos);

        /* adicionar itens na coluna seletor */
        if ( mapColunas.containsKey(TipoColuna.SELECTOR) )
            for ( int i = 0; i <= objetos.size(); i++ ) {
                selecoes.add(Boolean.FALSE);
            }

        if ( adicionou )
            fireTableDataChanged();
        return adicionou;

    }

    public boolean add(Pojo objeto) {
        // TODO objeto deve ser da mesma classe no construtor
        if ( !pClass.getClass().isAssignableFrom(objeto.getClass()) )
            return false;

        boolean adicionou = objetos.add(objeto);
        if ( adicionou )
            fireTableRowsInserted(getRowCount() - 1, getColumnCount() - 1);
        return adicionou;
    }

    public void setSelecionados(List<Object> objetosSelecionados) {
        for ( Object selecionado : objetosSelecionados ) {
            int index = objetos.indexOf(selecionado);
            selecoes.set(index, Boolean.TRUE);
        }
        // fireTableDataChanged();
    }

    public List<Object> getSelecionados() {
        List<Object> selecionados = new ArrayList<Object>();
        for ( int i = 0; i < objetos.size(); i++ ) {
            if ( selecoes.get(i) )
                selecionados.add(objetos.get(i));
        }
        return selecionados;
    }

    @Deprecated
    public List<Long> getIdsSelecionados() {
        return null;
    }

    public void deselectAll() {
        for ( int i = 0; i < objetos.size(); i++ )
            if ( selecoes.get(i) ) {
                selecoes.set(i, Boolean.FALSE);
                fireTableCellUpdated(i, getSelectorColumn());
            }
    }

    @Deprecated
    public void deselectOthers(int row){
        for ( int i = 0; i < objetos.size(); i++ )
            if ( i!= row && selecoes.get(i) ) {
                selecoes.set(i, Boolean.FALSE);
                fireTableCellUpdated(i, getSelectorColumn());
            }
    }

    public void selectAll() {
        for ( int i = 0; i < objetos.size(); i++ )
            if ( !selecoes.get(i) ) {
                selecoes.set(i, Boolean.TRUE);
                fireTableCellUpdated(i, getSelectorColumn());
            }
    }
}
